Skip to content

Comments

Feature: Git sync, set-tracking, and push divergence handling#796

Merged
gsxdsm merged 2 commits intoAutoMaker-Org:v0.15.0rcfrom
gsxdsm:feature/git-sync-set-tracking-divergence-handling
Feb 22, 2026
Merged

Feature: Git sync, set-tracking, and push divergence handling#796
gsxdsm merged 2 commits intoAutoMaker-Org:v0.15.0rcfrom
gsxdsm:feature/git-sync-set-tracking-divergence-handling

Conversation

@gsxdsm
Copy link
Collaborator

@gsxdsm gsxdsm commented Feb 22, 2026

Summary

  • Modernize push: Replace unsafe child_process.exec with safe execGitCommand array args in a new push-service.ts, matching the existing pull-service.ts pattern. Detects diverged branches and auto-resolves via pull-then-retry when autoResolve is enabled.
  • New Sync action: Composes pull + push in a single operation (sync-service.ts + POST /sync), with automatic stash management and divergence retry. Appears as a new menu item between Push and Merge & Rebase with split-button support for multiple remotes.
  • Set Tracking Branch: New POST /set-tracking endpoint. Each remote in the Pull/Push submenus now has a nested sub-menu with "Set as Tracking Branch" — no duplicate remote lists.

Changes

Server (4 new files, 2 modified)

  • apps/server/src/services/push-service.ts — New service with performPush(), divergence detection, auto-resolve
  • apps/server/src/services/sync-service.ts — New service composing performPull() + performPush()
  • apps/server/src/routes/worktree/routes/set-tracking.ts — New POST /set-tracking endpoint
  • apps/server/src/routes/worktree/routes/sync.ts — New POST /sync endpoint
  • apps/server/src/routes/worktree/routes/push.ts — Refactored to delegate to push-service
  • apps/server/src/routes/worktree/index.ts — Register new routes

Frontend (10 modified)

  • http-api-client.ts — Added sync(), setTracking(), updated push() with autoResolve
  • electron.d.ts — Type signatures for new API methods
  • electron.ts — Mock implementations
  • use-worktree-mutations.tsuseSyncWorktree(), useSetTracking() mutations
  • mutations/index.ts — Barrel exports
  • use-worktree-actions.tshandleSync, handleSetTracking, isSyncing
  • worktree-actions-dropdown.tsx — Sync menu item + per-remote "Set as Tracking Branch" sub-menus
  • worktree-tab.tsx, worktree-dropdown.tsx, worktree-panel.tsx — Prop wiring

Test plan

  • Build passes: npm run build:packages && npm run build:server
  • TypeScript check passes (no new errors)
  • Lint passes: npm run lint (0 errors)
  • Push with divergent branch triggers auto-resolve when enabled
  • "Set as Tracking Branch" appears in each remote's sub-menu in Pull/Push dropdowns
  • "Sync" action works with single and multiple remotes
  • Existing push/pull behavior unchanged for single-remote repos

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Worktree sync: one-step pull-then-push synchronization with conflict reporting and retry/auto-resolve options.
    • Set upstream/tracking branch for worktrees from the UI.
    • Enhanced push: richer success/error responses including divergence, conflicts, and optional auto-resolve.
    • UI: per-remote sync/push/pull actions, tracking indicator, and syncing state with spinner/disabled states.

- Create push-service.ts replacing unsafe child_process.exec with safe
  execGitCommand array args; detect diverged branches and auto-resolve
  via pull-then-retry when autoResolve is enabled
- Create sync-service.ts composing pull then push with automatic stash
  management and divergence retry
- Add POST /set-tracking endpoint to set upstream tracking branch
- Add POST /sync endpoint for pull+push in one operation
- Refactor push route to delegate to push-service (thin handler pattern)
- Add sync(), setTracking() to frontend API client, mutations, and hooks
- Add Sync menu item (with split-button for multiple remotes) between
  Push and Merge & Rebase in the worktree actions dropdown
- Add "Set as Tracking Branch" sub-menu on each remote in Pull/Push
  remote submenus (no duplicate remote lists)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @gsxdsm, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the Git integration by modernizing push operations, introducing a comprehensive sync feature, and enabling explicit tracking branch management. These changes aim to provide a more robust, safer, and user-friendly experience for managing Git repositories, particularly when dealing with divergent branches and synchronizing changes with remotes.

Highlights

  • Modernized Git Push Logic: The push operation has been refactored to use a new push-service.ts for improved safety and consistency, replacing child_process.exec with execGitCommand. It now includes automatic detection and resolution of divergent branches via a pull-then-retry mechanism when autoResolve is enabled.
  • New Sync Action: A new sync action has been introduced, which orchestrates a pull followed by a push operation. This action includes automatic stash management and handles divergence retries, providing a single, streamlined workflow for synchronizing local and remote branches. It is exposed via a new POST /sync endpoint and integrated into the UI.
  • Set Tracking Branch Feature: Users can now explicitly set the upstream tracking branch for their local branches. This functionality is exposed through a new POST /set-tracking endpoint and integrated into the UI's remote sub-menus for both pull and push operations.
  • Frontend Integration: The UI has been updated to support the new Sync and Set Tracking Branch features, including new dropdown menu items, associated hooks (useSyncWorktree, useSetTracking), and updated type definitions for the Electron API.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • apps/server/src/routes/worktree/index.ts
    • Registered new server routes for /sync and /set-tracking.
  • apps/server/src/routes/worktree/routes/push.ts
    • Refactored push handler to delegate Git logic to push-service.ts.
    • Replaced child_process.exec with execGitCommand for safer execution.
    • Added autoResolve option to the push request body.
    • Introduced isClientError helper function to determine appropriate HTTP status codes for push errors.
  • apps/server/src/routes/worktree/routes/set-tracking.ts
    • Added new server route to handle POST /set-tracking requests.
    • Implemented logic to set the upstream tracking branch using git branch --set-upstream-to.
  • apps/server/src/routes/worktree/routes/sync.ts
    • Added new server route to handle POST /sync requests.
    • Implemented logic to delegate sync operations to sync-service.ts.
  • apps/server/src/services/push-service.ts
    • Added a new service performPush to encapsulate Git push logic.
    • Implemented divergence detection and auto-resolution via pull-then-retry.
    • Handled detached HEAD state and structured push results.
  • apps/server/src/services/sync-service.ts
    • Added a new service performSync to orchestrate pull and push operations.
    • Ensured stashIfNeeded is used for pull and handled push divergence retries.
    • Provided structured results including conflict information.
  • apps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsx
    • Added GitBranch icon import.
    • Extended WorktreeActionsDropdownProps with isSyncing, onSync, onSyncWithRemote, and onSetTracking.
    • Integrated 'Sync' menu item with split-button support for multiple remotes.
    • Integrated 'Set as Tracking Branch' sub-menu item within remote-specific pull/push menus.
  • apps/ui/src/components/views/board-view/worktree-panel/components/worktree-dropdown.tsx
    • Extended WorktreeDropdownProps with isSyncing, onSync, onSyncWithRemote, and onSetTracking.
    • Passed new sync and set tracking props to WorktreeActionsDropdown.
  • apps/ui/src/components/views/board-view/worktree-panel/components/worktree-tab.tsx
    • Extended WorktreeTabProps with isSyncing, onSync, onSyncWithRemote, and onSetTracking.
    • Passed new sync and set tracking props to WorktreeActionsDropdown.
  • apps/ui/src/components/views/board-view/worktree-panel/hooks/use-worktree-actions.ts
    • Imported useSyncWorktree and useSetTracking mutations.
    • Initialized syncMutation and setTrackingMutation hooks.
    • Added handleSync and handleSetTracking callback functions.
    • Exposed isSyncing, handleSync, and handleSetTracking from the hook.
  • apps/ui/src/components/views/board-view/worktree-panel/worktree-panel.tsx
    • Destructured isSyncing, handleSync, and handleSetTracking from useWorktreeActions.
    • Implemented handleSyncWithRemoteSelection, handleSyncWithSpecificRemote, and handleSetTrackingForRemote callbacks.
    • Passed new sync and set tracking related props to WorktreeActionsDropdown and WorktreeTab components.
  • apps/ui/src/hooks/mutations/index.ts
    • Exported useSyncWorktree and useSetTracking mutations.
  • apps/ui/src/hooks/mutations/use-worktree-mutations.ts
    • Added useSyncWorktree mutation for performing pull-then-push operations.
    • Added useSetTracking mutation for setting upstream tracking branches.
  • apps/ui/src/lib/electron.ts
    • Updated mock push function to include _autoResolve parameter.
    • Added mock sync function for simulating sync operations.
    • Added mock setTracking function for simulating setting tracking branches.
  • apps/ui/src/lib/http-api-client.ts
    • Updated push API client method to include autoResolve parameter.
    • Added sync API client method for the new sync endpoint.
    • Added setTracking API client method for the new set tracking endpoint.
  • apps/ui/src/types/electron.d.ts
    • Updated push interface to include autoResolve, diverged, hasConflicts, and conflictFiles in the result.
    • Added sync interface for the new sync API, including detailed result types.
    • Added setTracking interface for the new set tracking API, including detailed result types.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@coderabbitai
Copy link

coderabbitai bot commented Feb 22, 2026

📝 Walkthrough

Walkthrough

Adds server endpoints and services for worktree sync and tracking, refactors push to a push-service with auto-resolve, and exposes sync/set-tracking actions through UI components, hooks, and API client/type updates.

Changes

Cohort / File(s) Summary
Worktree routes
apps/server/src/routes/worktree/index.ts, apps/server/src/routes/worktree/routes/sync.ts, apps/server/src/routes/worktree/routes/set-tracking.ts, apps/server/src/routes/worktree/routes/push.ts
Registered new POST /sync and POST /set-tracking routes; push route now delegates to push-service and accepts autoResolve, with structured success/error/divergence responses.
Git operation services
apps/server/src/services/push-service.ts, apps/server/src/services/sync-service.ts
New performPush and performSync services implementing push/pull orchestration, divergence detection, auto-resolve (pull-then-retry), and detailed result types.
UI components
apps/ui/src/components/.../worktree-actions-dropdown.tsx, apps/ui/src/components/.../worktree-dropdown.tsx, apps/ui/src/components/.../worktree-tab.tsx, apps/ui/src/components/.../worktree-panel.tsx
Added syncing/tracking props and handlers (isSyncing, onSync, onSyncWithRemote, onSetTracking); UI shows sync options, per-remote actions, and tracking submenu/indicator.
UI hooks & mutations
apps/ui/src/components/.../hooks/use-worktree-actions.ts, apps/ui/src/hooks/mutations/use-worktree-mutations.ts, apps/ui/src/hooks/mutations/index.ts
Added useSyncWorktree and useSetTracking mutations, handleSync/handleSetTracking handlers, isSyncing state, toasts and worktrees query invalidation.
API client, mocks & types
apps/ui/src/lib/http-api-client.ts, apps/ui/src/lib/electron.ts, apps/ui/src/types/electron.d.ts
Http API and Electron mock extended with sync and setTracking endpoints; push signature accepts autoResolve and result types include diverged/autoResolved/conflict fields.
Minor changes
(various small edits)
Input validation and error shaping added across new routes and services; wiring and prop propagation across UI layers.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant UI as Worktree Panel
    participant Hook as useWorktreeActions
    participant Mutation as useSyncWorktree
    participant API as HTTP API Client
    participant Server as /api/worktree/sync
    participant SyncSvc as Sync Service
    participant PullSvc as Pull Service
    participant PushSvc as Push Service
    participant Git as Git Worktree

    User->>UI: Click "Sync"
    UI->>Hook: handleSync(worktree, remote)
    Hook->>Mutation: mutate({ worktreePath, remote })
    Mutation->>API: POST /api/worktree/sync
    API->>Server: HTTP request
    Server->>SyncSvc: performSync(worktreePath, { remote })
    SyncSvc->>PullSvc: performPull(..., stashIfNeeded)
    PullSvc->>Git: git pull
    Git-->>PullSvc: pull result (ok/conflict)
    PullSvc-->>SyncSvc: pull result
    SyncSvc->>PushSvc: performPush(..., autoResolve?)
    PushSvc->>Git: git push
    Git-->>PushSvc: push result (ok/diverged/error)
    PushSvc-->>SyncSvc: push result
    SyncSvc-->>Server: SyncResult
    Server-->>API: JSON response
    API-->>Mutation: success/error
    Mutation->>UI: invalidate queries, show toast
    UI->>User: Display result
Loading
sequenceDiagram
    actor User
    participant UI as Worktree Panel
    participant Hook as useWorktreeActions
    participant Mutation as usePushMutation
    participant API as HTTP API Client
    participant Server as /api/worktree/push
    participant PushSvc as Push Service
    participant Git as Git Worktree

    User->>UI: Click "Push" (autoResolve)
    UI->>Hook: handlePush(worktree, remote, autoResolve)
    Hook->>Mutation: mutate({ worktreePath, remote, autoResolve })
    Mutation->>API: POST /api/worktree/push {autoResolve:true}
    API->>Server: HTTP request
    Server->>PushSvc: performPush(worktreePath, { remote, autoResolve })
    PushSvc->>Git: getCurrentBranch()
    Git-->>PushSvc: branch name / detached
    PushSvc->>Git: git push
    Git-->>PushSvc: divergence error
    alt autoResolve == true
        PushSvc->>PushSvc: performPull({ stashIfNeeded })
        PushSvc->>Git: git pull
        Git-->>PushSvc: pull result
        PushSvc->>Git: git push (retry)
        Git-->>PushSvc: push result
    end
    PushSvc-->>Server: PushResult
    Server-->>API: JSON response
    API-->>Mutation: success/error
    Mutation->>UI: invalidate queries, show toast
    UI->>User: Show push outcome
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

Enhancement, scope: ui

Poem

🐰 I hop and nudge the branches tight,

Pull then push to make things right,
If divergence dares to play,
I stash, I merge, I clear the way—
Hooray, worktrees dance in sync tonight! 🌙

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 58.82% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Feature: Git sync, set-tracking, and push divergence handling' directly and accurately summarizes the main changes: it introduces sync and set-tracking features and improves push with divergence handling.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces significant improvements to the Git workflow by adding sync and set-tracking functionalities, and modernizing the push operation. The changes are well-structured, with business logic correctly encapsulated in new services on the backend. The frontend updates are comprehensive, wiring up the new capabilities through the component hierarchy. Overall, this is a solid feature addition. I've identified a couple of areas for improvement: one regarding redundant logic in the new push-service and another concerning code duplication in a React component, which could be refactored for better maintainability.

Comment on lines 218 to 240
// 6b. Non-divergence error - try with --set-upstream
try {
const setUpstreamArgs = ['push', '--set-upstream', targetRemote, branchName];
if (force) {
setUpstreamArgs.push('--force');
}
await execGitCommand(setUpstreamArgs, worktreePath);

return {
success: true,
branch: branchName,
pushed: true,
message: `Successfully pushed ${branchName} to ${targetRemote} (set upstream)`,
};
} catch (upstreamError: unknown) {
const upstreamErr = upstreamError as { stderr?: string; message?: string };
return {
success: false,
branch: branchName,
pushed: false,
error: upstreamErr.stderr || upstreamErr.message || getErrorMessage(pushError),
};
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The fallback logic to try git push --set-upstream is redundant. The initial push attempt on line 113 already uses the -u flag, which is an alias for --set-upstream. If the first push attempt fails for a reason other than divergence, this second attempt will also fail for the same reason.

To improve clarity and remove redundant code, it would be better to remove this try/catch block and just return the original error if it's not a divergence error.

    // 6b. Non-divergence error
    return {
      success: false,
      branch: branchName,
      pushed: false,
      error: err.stderr || err.message || getErrorMessage(pushError),
    };

Comment on lines 869 to 918
{remotes.map((remote) =>
onSetTracking ? (
<DropdownMenuSub key={remote.name}>
<div className="flex items-center">
<DropdownMenuItem
onClick={() =>
isGitOpsAvailable && onPushWithRemote(worktree, remote.name)
}
disabled={isPushing || !isGitOpsAvailable}
className="text-xs flex-1 pr-0 rounded-r-none"
>
<Upload className="w-3.5 h-3.5 mr-2" />
{remote.name}
{trackingRemote === remote.name && (
<span className="ml-auto text-[10px] text-muted-foreground mr-1">
tracking
</span>
)}
</DropdownMenuItem>
<DropdownMenuSubTrigger
className="text-xs px-1 rounded-l-none border-l border-border/30 h-8"
disabled={!isGitOpsAvailable}
/>
</div>
<DropdownMenuSubContent>
<DropdownMenuItem
onClick={() => isGitOpsAvailable && onSetTracking(worktree, remote.name)}
disabled={!isGitOpsAvailable}
className="text-xs"
>
<GitBranch className="w-3.5 h-3.5 mr-2" />
Set as Tracking Branch
</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuSub>
) : (
<DropdownMenuItem
key={remote.name}
onClick={() => isGitOpsAvailable && onPushWithRemote(worktree, remote.name)}
disabled={isPushing || !isGitOpsAvailable}
className="text-xs"
>
<Upload className="w-3.5 h-3.5 mr-2" />
{remote.name}
<span className="ml-auto text-[10px] text-muted-foreground max-w-[100px] truncate">
{remote.url}
</span>
</DropdownMenuItem>
)
)}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This block of code for rendering the push remote items is nearly identical to the block for rendering the pull remote items (lines 734-783). This duplication makes the component harder to maintain.

To improve maintainability, consider extracting this logic into a reusable component, for example RemoteActionMenuItem. This component could accept props like the remote, the action handler (onPullWithRemote or onPushWithRemote), the loading state (isPulling or isPushing), and the icon (Download or Upload) to render the appropriate menu item.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (12)
apps/ui/src/types/electron.d.ts (1)

1001-1020: Consider whether diverged?: boolean should be surfaced at the top level of sync().

The push() return type exposes a top-level diverged?: boolean for cases where auto-resolve fails. sync() omits this field. In the normal flow the pull phase resolves divergence, but a narrow race (remote advances again between pull and push) could leave consumers unable to distinguish a divergence error from any other error without parsing the error string.

If the server guarantees that scenario cannot occur (e.g. push-service retries are bounded and any remaining divergence becomes a generic error), this omission is intentional — a brief comment in the type or JSDoc would confirm that.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ui/src/types/electron.d.ts` around lines 1001 - 1020, Add a top-level
optional diverged?: boolean to the sync() return shape (the Promise result
object) to mirror push() and allow callers to detect a push-divergence race
without parsing error strings; update the sync declaration (function name: sync)
to include diverged?: boolean alongside the existing success/error/hasConflicts
fields, or alternatively add a JSDoc note on sync explaining why divergence is
impossible if you intend to keep the current type.
apps/ui/src/lib/electron.ts (1)

2303-2319: Hardcoded branch fallback in mock can mislead during development.

When branch is undefined, the real server resolves the current branch dynamically. The mock always returns 'feature-branch', which may confuse developers testing the "no branch specified" path. A small comment or a more descriptive placeholder (e.g., 'current-branch') would clarify intent.

✏️ Suggested tweak
-    const targetBranch = branch || 'feature-branch';
+    // In production the server resolves to the current branch; mock uses a placeholder
+    const targetBranch = branch || 'current-branch';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ui/src/lib/electron.ts` around lines 2303 - 2319, The mock
implementation of setTracking uses a misleading hardcoded fallback
'feature-branch' when branch is undefined; update the function (setTracking) to
use a clearer placeholder like 'current-branch' or add a comment indicating this
is a dynamic stand-in for the real server-resolved branch, and ensure the
returned result fields (branch, upstream, message) reflect that placeholder so
developers testing the "no branch specified" path aren't confused by a concrete
feature name.
apps/ui/src/hooks/mutations/use-worktree-mutations.ts (1)

208-227: useSyncWorktree discards structured conflict information on failure.

The API returns hasConflicts, conflictFiles, and conflictSource ('pull' | 'stash') at the top level, but the mutation only forwards the error string. Sync spans two phases (pull + stash-pop + push), so the source of a conflict is more actionable here than for a simple pull or push.

While the existing usePullWorktree and usePushWorktree have the same gap, sync is a higher-level operation where callers would benefit most from this context — for example, to show "conflict occurred while restoring stash" vs. "conflict occurred while pulling".

♻️ Proposed improvement
-      if (!result.success) {
-        throw new Error(result.error || 'Failed to sync');
-      }
-      return result.result;
+      if (!result.success) {
+        const err = Object.assign(
+          new Error(result.error || 'Failed to sync'),
+          {
+            hasConflicts: result.hasConflicts,
+            conflictFiles: result.conflictFiles,
+            conflictSource: result.conflictSource,
+          }
+        );
+        throw err;
+      }
+      return result.result;

Then in onError, the enriched fields can be inspected similarly to how useSwitchBranch surfaces stashPopConflicts.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ui/src/hooks/mutations/use-worktree-mutations.ts` around lines 208 -
227, The mutation currently throws a plain Error with only result.error,
discarding structured fields like hasConflicts, conflictFiles, and
conflictSource returned by the API; update the mutationFn in
use-worktree-mutations (the useSyncWorktree mutation) to throw the original
result object (or a custom error object that preserves result.hasConflicts,
result.conflictFiles, result.conflictSource and result.error) instead of new
Error(result.error), and adjust the onError handler signature in the same file
to accept the thrown object (inspect error.hasConflicts, error.conflictFiles,
error.conflictSource) so callers can differentiate conflictSource (e.g., 'pull'
vs 'stash') when handling sync failures.
apps/server/src/routes/worktree/routes/sync.ts (1)

35-46: Consider including branch in error response for client context.

performSync populates result.branch in most failure paths (pull failure, conflict, push-diverge retry). Omitting it from the error response means the client receives no branch context on failure. A simple addition:

💡 Suggested improvement
         res.status(statusCode).json({
           success: false,
+          branch: result.branch,
           error: result.error,
           hasConflicts: result.hasConflicts,
           conflictFiles: result.conflictFiles,
           conflictSource: result.conflictSource,
           pulled: result.pulled,
           pushed: result.pushed,
         });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/routes/worktree/routes/sync.ts` around lines 35 - 46, The
error response JSON in the sync route currently omits branch context even though
performSync sets result.branch on failures; update the error response in the
failure block (the code that checks if (!result.success) and builds the JSON
with success, error, hasConflicts, etc.) to include branch: result.branch so the
client receives branch information on pull/conflict/push-diverge failures.
apps/server/src/routes/worktree/routes/set-tracking.ts (1)

15-75: Business logic in route handler violates server layering guideline.

getCurrentBranch() and execGitCommand() are called directly in the route handler. The analogous /sync route correctly delegates all Git logic to sync-service.ts. A set-tracking-service.ts (or adding performSetTracking() to an existing service) would keep the server layer consistent and testable.

As per coding guidelines: "Server business logic should be organized into services in the services/ directory, with Express route handlers in routes/ that delegate to services."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/routes/worktree/routes/set-tracking.ts` around lines 15 - 75,
Move all Git/business logic out of createSetTrackingHandler and into a new
service (e.g., services/set-tracking-service.ts) or add a
performSetTracking(worktreePath, remote, branch?) function to an existing
service; the handler should only validate req.body, call the service function
(which will call getCurrentBranch and execGitCommand, handle the detached HEAD
check, and throw meaningful errors), and translate the service result to the
HTTP response. Ensure the service returns a structured result matching the
current response shape (branch, remote, upstream, message) and that the route
catches service errors, logs with logError('Set tracking branch failed', error)
and returns getErrorMessage(error) in the JSON error payload.
apps/ui/src/components/views/board-view/worktree-panel/hooks/use-worktree-actions.ts (1)

241-261: setTrackingMutation.isPending is not exposed — UI cannot show loading state.

isSyncing is surfaced but there is no isSettingTracking, so consumers have no way to disable the "Set as Tracking Branch" menu item or show a spinner while the mutation is in-flight. The mutation itself prevents double-submission, but the missing state creates a silent UX gap.

💡 Suggested addition
   return {
     isPulling: pullMutation.isPending,
     isPushing: pushMutation.isPending,
     isSyncing: syncMutation.isPending,
+    isSettingTracking: setTrackingMutation.isPending,
     isSwitching: switchBranchMutation.isPending,
     ...
   };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/ui/src/components/views/board-view/worktree-panel/hooks/use-worktree-actions.ts`
around lines 241 - 261, The return object from use-worktree-actions.ts is
missing the pending state for the setTrackingMutation; add an isSettingTracking
property (set to setTrackingMutation.isPending) alongside the other flags so
consumers can disable the "Set as Tracking Branch" UI and show a spinner while
handleSetTracking runs; update the exported return to include isSettingTracking
and ensure handleSetTracking remains unchanged.
apps/server/src/routes/worktree/routes/push.ts (1)

34-44: HTTP 400 for a diverged push is inconsistent with the 409 used by /sync for conflicts.

isClientError maps 'diverged' (and 'rejected') to HTTP 400. A diverged branch is not a malformed request — the client sent a valid worktreePath/remote pair. The /sync route correctly returns 409 Conflict when merge conflicts occur. Using 409 for diverged pushes too would align the two endpoints and give clients a uniform signal to retry after rebasing/pulling.

💡 Suggested fix
-      if (!result.success) {
-        const statusCode = isClientError(result.error ?? '') ? 400 : 500;
-        res.status(statusCode).json({
+      if (!result.success) {
+        const statusCode = result.diverged || result.hasConflicts ? 409 : 500;
+        res.status(statusCode).json({
           success: false,
           error: result.error,
           diverged: result.diverged,
           hasConflicts: result.hasConflicts,
           conflictFiles: result.conflictFiles,
         });
         return;
       }

And remove the now-unused isClientError helper.

Also applies to: 67-72

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/routes/worktree/routes/push.ts` around lines 34 - 44, When
handling a failed push in the push route (the block checking result.success and
using isClientError), treat a result.error value of 'diverged' as HTTP 409
Conflict instead of 400: compute statusCode as 409 when result.error ===
'diverged' (keep 400 for other client-side validation errors and 500 for server
errors), update both error-response sites (the blocks around result.success and
the later block at ~67-72) to use this mapping, and then remove the now-unused
isClientError helper to avoid dead code.
apps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsx (1)

734-783: Nearly identical remote-submenu rendering in Pull and Push sections.

The two blocks for rendering per-remote entries with "Set as Tracking Branch" are structurally identical — differing only in the icon (Download/Upload), the handler (onPullWithRemote/onPushWithRemote), and the disabled-state source (isPulling/isPushing). If this pattern grows further (e.g., sync remotes also gaining set-tracking), consider extracting a shared RemoteSubMenuItems component to reduce duplication.

Also applies to: 869-918

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsx`
around lines 734 - 783, Extract the duplicated per-remote UI into a reusable
component (e.g., RemoteSubMenuItems) and replace the two near-identical map
blocks with calls to that component; the component should accept props: remotes,
worktree, onAction (handler for clicking remote e.g., onPullWithRemote or
onPushWithRemote), actionDisabled (boolean or function per-remote using
isPulling/isPushing), actionIcon (Download/Upload), onSetTracking,
trackingRemote, and isGitOpsAvailable, and render the same
DropdownMenuItem/DropdownMenuSub structure (including the Set as Tracking Branch
item) so the only differences between the Pull and Push usages are the icon
prop, the onAction prop, and the actionDisabled prop.
apps/server/src/services/sync-service.ts (2)

140-185: Double-pull scenario on divergence is acceptable but worth documenting.

When push fails with divergence (Line 142), the retry calls performPush with autoResolve: true, which internally does another pull-then-push. This means the sync performs: pull → push (rejected) → pull (again) → push (retry). This is a sound one-retry strategy for handling a race between pull and push, but it's worth a brief inline comment noting the intentional double-pull so future maintainers don't try to "optimize" it away.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/services/sync-service.ts` around lines 140 - 185, The retry
branch that triggers when pushResult.diverged intentionally calls performPush
with autoResolve: true which internally performs another pull-then-push,
resulting in a pull → push (rejected) → pull → push sequence; update the code
around the if (pushResult.diverged) block (reference pushResult.diverged and
performPush) with a concise inline comment explaining that the second pull is
intentional to resolve races and must not be removed or "optimized" away, and
mention getErrorMessage/pullResult as context for the surrounding error
handling.

68-208: Events for sync progress tracking are not currently emitted.

The coding guidelines state that server operations should emit events via createEventEmitter() for WebSocket streaming to the frontend. However, sync-service.ts (along with pull-service.ts and push-service.ts) does not follow this pattern. Adding event emission would require passing an EventEmitter to the route handler and emitting progress events such as sync:started, sync:pull-complete, and sync:completed to provide real-time UI feedback during the multi-step sync operation.

Note: This is consistent with the existing pull/push family of operations, which are currently designed as simple request-response endpoints without event streaming. Implementing this would require aligning with operations like rebase and stash that already use this pattern.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/services/sync-service.ts` around lines 68 - 208, performSync
currently runs a multi-step pull+push flow without emitting progress events;
update performSync to accept an EventEmitter (or create one via
createEventEmitter passed from the route) and emit structured events at key
points: emit 'sync:started' at start (include worktreePath and remote),
'sync:pull-complete' after performPull returns (include pullResult and any
conflict info), 'sync:push-start' before performPush, 'sync:push-retry' when
retrying with autoResolve, and terminal events 'sync:completed' or 'sync:failed'
with the final SyncResult; ensure performPull and performPush call sites in this
function remain unchanged but wire the emitter into retries and catch blocks so
errors use getErrorMessage and the emitter to stream failures (reference
performSync, performPull, performPush, getErrorMessage, and createEventEmitter).
apps/server/src/services/push-service.ts (1)

112-116: Every push unconditionally sets -u (upstream tracking).

Using -u on every push means that pushing to any remote will silently re-configure the upstream tracking branch. If a user pushes to a secondary remote (e.g., a fork), this would change their tracking away from the primary remote. Given that this PR also adds explicit set-tracking functionality, the implicit -u here could conflict with the user's intent.

Consider reserving -u for the fallback path only (when no upstream is configured) or making it opt-in.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/services/push-service.ts` around lines 112 - 116, The
pushArgs currently always include '-u', which can overwrite a branch's upstream;
change the push preparation in push-service.ts so '-u' is only added when needed
— either when the caller explicitly requests tracking (e.g., a new opt-in flag
like setTracking) or when the branch has no configured upstream; to implement,
before pushing check the branch's upstream status (via the existing helper or a
git command) and only push with '-u', e.g., pushArgs = ['push', targetRemote,
branchName] and conditionally pushArgs.unshift('-u') or pushArgs.push('-u') when
upstream is absent or setTracking===true; keep the existing force handling on
the pushArgs array.
apps/ui/src/components/views/board-view/worktree-panel/worktree-panel.tsx (1)

834-856: Trivial pass-through wrappers can be simplified.

handleSyncWithRemoteSelection, handleSyncWithSpecificRemote, and handleSetTrackingForRemote are identity wrappers with no added logic. Unlike the analogous handlePullWithRemoteSelection (which checks the tracking remote and may show a dialog), handleSyncWithRemoteSelection just forwards to handleSync — so the name is misleading. You could pass handleSync and handleSetTracking directly as props, or rename the sync wrapper to reflect that it doesn't perform remote selection.

♻️ Simplification (pass directly)

Where these are used as props, replace them with the hook-provided handlers:

-            onSync={handleSyncWithRemoteSelection}
-            onSyncWithRemote={handleSyncWithSpecificRemote}
-            onSetTracking={handleSetTrackingForRemote}
+            onSync={handleSync}
+            onSyncWithRemote={handleSync}
+            onSetTracking={handleSetTracking}

And remove the three wrapper useCallbacks at lines 834–856.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ui/src/components/views/board-view/worktree-panel/worktree-panel.tsx`
around lines 834 - 856, The three useCallback pass-throughs
(handleSyncWithRemoteSelection, handleSyncWithSpecificRemote,
handleSetTrackingForRemote) are unnecessary wrappers around handleSync and
handleSetTracking; remove these three functions and update call sites to pass
handleSync and handleSetTracking directly (or, if a different signature is
required, rename handleSyncWithRemoteSelection to clearly indicate it doesn't
perform selection). Ensure any props or handlers that previously expected the
wrapper signatures now accept the original handleSync(worktree[, remote]) and
handleSetTracking(worktree, remote) functions so there are no mismatched
parameters.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/server/src/routes/worktree/routes/set-tracking.ts`:
- Around line 34-60: The code uses
execGitCommand(['branch','--set-upstream-to',`${remote}/${targetBranch}`],
worktreePath) which only changes the upstream of the currently checked-out
branch; to apply the upstream to the named local branch you must pass that
branch name as the final positional argument. Update the execGitCommand
invocation in set-tracking.ts (the call using targetBranch) to include
targetBranch as the last argument (i.e. ['branch','--set-upstream-to',
`${remote}/${targetBranch}`, targetBranch]) and ensure targetBranch is
validated/derived the same way as before.

In `@apps/server/src/services/push-service.ts`:
- Around line 218-240: The fallback with --set-upstream duplicates the initial
push's -u flag; change the initial push call that builds args with '-u' so it
performs a plain push (remove '-u') and leave the fallback logic
(setUpstreamArgs + execGitCommand) intact so it only runs when upstream is truly
missing; locate the initial push invocation that calls execGitCommand (the
earlier push path that currently adds '-u') and remove the '-u' flag there, and
keep the fallback block using setUpstreamArgs/execGitCommand as-is (ensure
returned messages still reference branchName/targetRemote and that error returns
preserve pushError when appropriate).
- Around line 56-64: isDivergenceError currently treats any output containing
"rejected" as divergence which causes false positives; update the function to
only classify as divergence when stronger markers are present (e.g.,
"non-fast-forward", "failed to push some refs", "fetch first") or when
"rejected" appears together with other divergence indicators (e.g., "rejected" +
"non-fast-forward"/"fetch"); modify the isDivergenceError(errorOutput: string)
logic to require either specific phrases or a combination of markers (and
explicitly exclude common server-side rejection text like "pre-receive hook" or
"protected branch") so only true non-fast-forward/divergence cases trigger the
pull-then-retry path.

---

Nitpick comments:
In `@apps/server/src/routes/worktree/routes/push.ts`:
- Around line 34-44: When handling a failed push in the push route (the block
checking result.success and using isClientError), treat a result.error value of
'diverged' as HTTP 409 Conflict instead of 400: compute statusCode as 409 when
result.error === 'diverged' (keep 400 for other client-side validation errors
and 500 for server errors), update both error-response sites (the blocks around
result.success and the later block at ~67-72) to use this mapping, and then
remove the now-unused isClientError helper to avoid dead code.

In `@apps/server/src/routes/worktree/routes/set-tracking.ts`:
- Around line 15-75: Move all Git/business logic out of createSetTrackingHandler
and into a new service (e.g., services/set-tracking-service.ts) or add a
performSetTracking(worktreePath, remote, branch?) function to an existing
service; the handler should only validate req.body, call the service function
(which will call getCurrentBranch and execGitCommand, handle the detached HEAD
check, and throw meaningful errors), and translate the service result to the
HTTP response. Ensure the service returns a structured result matching the
current response shape (branch, remote, upstream, message) and that the route
catches service errors, logs with logError('Set tracking branch failed', error)
and returns getErrorMessage(error) in the JSON error payload.

In `@apps/server/src/routes/worktree/routes/sync.ts`:
- Around line 35-46: The error response JSON in the sync route currently omits
branch context even though performSync sets result.branch on failures; update
the error response in the failure block (the code that checks if
(!result.success) and builds the JSON with success, error, hasConflicts, etc.)
to include branch: result.branch so the client receives branch information on
pull/conflict/push-diverge failures.

In `@apps/server/src/services/push-service.ts`:
- Around line 112-116: The pushArgs currently always include '-u', which can
overwrite a branch's upstream; change the push preparation in push-service.ts so
'-u' is only added when needed — either when the caller explicitly requests
tracking (e.g., a new opt-in flag like setTracking) or when the branch has no
configured upstream; to implement, before pushing check the branch's upstream
status (via the existing helper or a git command) and only push with '-u', e.g.,
pushArgs = ['push', targetRemote, branchName] and conditionally
pushArgs.unshift('-u') or pushArgs.push('-u') when upstream is absent or
setTracking===true; keep the existing force handling on the pushArgs array.

In `@apps/server/src/services/sync-service.ts`:
- Around line 140-185: The retry branch that triggers when pushResult.diverged
intentionally calls performPush with autoResolve: true which internally performs
another pull-then-push, resulting in a pull → push (rejected) → pull → push
sequence; update the code around the if (pushResult.diverged) block (reference
pushResult.diverged and performPush) with a concise inline comment explaining
that the second pull is intentional to resolve races and must not be removed or
"optimized" away, and mention getErrorMessage/pullResult as context for the
surrounding error handling.
- Around line 68-208: performSync currently runs a multi-step pull+push flow
without emitting progress events; update performSync to accept an EventEmitter
(or create one via createEventEmitter passed from the route) and emit structured
events at key points: emit 'sync:started' at start (include worktreePath and
remote), 'sync:pull-complete' after performPull returns (include pullResult and
any conflict info), 'sync:push-start' before performPush, 'sync:push-retry' when
retrying with autoResolve, and terminal events 'sync:completed' or 'sync:failed'
with the final SyncResult; ensure performPull and performPush call sites in this
function remain unchanged but wire the emitter into retries and catch blocks so
errors use getErrorMessage and the emitter to stream failures (reference
performSync, performPull, performPush, getErrorMessage, and createEventEmitter).

In
`@apps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsx`:
- Around line 734-783: Extract the duplicated per-remote UI into a reusable
component (e.g., RemoteSubMenuItems) and replace the two near-identical map
blocks with calls to that component; the component should accept props: remotes,
worktree, onAction (handler for clicking remote e.g., onPullWithRemote or
onPushWithRemote), actionDisabled (boolean or function per-remote using
isPulling/isPushing), actionIcon (Download/Upload), onSetTracking,
trackingRemote, and isGitOpsAvailable, and render the same
DropdownMenuItem/DropdownMenuSub structure (including the Set as Tracking Branch
item) so the only differences between the Pull and Push usages are the icon
prop, the onAction prop, and the actionDisabled prop.

In
`@apps/ui/src/components/views/board-view/worktree-panel/hooks/use-worktree-actions.ts`:
- Around line 241-261: The return object from use-worktree-actions.ts is missing
the pending state for the setTrackingMutation; add an isSettingTracking property
(set to setTrackingMutation.isPending) alongside the other flags so consumers
can disable the "Set as Tracking Branch" UI and show a spinner while
handleSetTracking runs; update the exported return to include isSettingTracking
and ensure handleSetTracking remains unchanged.

In `@apps/ui/src/components/views/board-view/worktree-panel/worktree-panel.tsx`:
- Around line 834-856: The three useCallback pass-throughs
(handleSyncWithRemoteSelection, handleSyncWithSpecificRemote,
handleSetTrackingForRemote) are unnecessary wrappers around handleSync and
handleSetTracking; remove these three functions and update call sites to pass
handleSync and handleSetTracking directly (or, if a different signature is
required, rename handleSyncWithRemoteSelection to clearly indicate it doesn't
perform selection). Ensure any props or handlers that previously expected the
wrapper signatures now accept the original handleSync(worktree[, remote]) and
handleSetTracking(worktree, remote) functions so there are no mismatched
parameters.

In `@apps/ui/src/hooks/mutations/use-worktree-mutations.ts`:
- Around line 208-227: The mutation currently throws a plain Error with only
result.error, discarding structured fields like hasConflicts, conflictFiles, and
conflictSource returned by the API; update the mutationFn in
use-worktree-mutations (the useSyncWorktree mutation) to throw the original
result object (or a custom error object that preserves result.hasConflicts,
result.conflictFiles, result.conflictSource and result.error) instead of new
Error(result.error), and adjust the onError handler signature in the same file
to accept the thrown object (inspect error.hasConflicts, error.conflictFiles,
error.conflictSource) so callers can differentiate conflictSource (e.g., 'pull'
vs 'stash') when handling sync failures.

In `@apps/ui/src/lib/electron.ts`:
- Around line 2303-2319: The mock implementation of setTracking uses a
misleading hardcoded fallback 'feature-branch' when branch is undefined; update
the function (setTracking) to use a clearer placeholder like 'current-branch' or
add a comment indicating this is a dynamic stand-in for the real server-resolved
branch, and ensure the returned result fields (branch, upstream, message)
reflect that placeholder so developers testing the "no branch specified" path
aren't confused by a concrete feature name.

In `@apps/ui/src/types/electron.d.ts`:
- Around line 1001-1020: Add a top-level optional diverged?: boolean to the
sync() return shape (the Promise result object) to mirror push() and allow
callers to detect a push-divergence race without parsing error strings; update
the sync declaration (function name: sync) to include diverged?: boolean
alongside the existing success/error/hasConflicts fields, or alternatively add a
JSDoc note on sync explaining why divergence is impossible if you intend to keep
the current type.

…g, extract duplicate rendering

- Tighten isDivergenceError() to require specific marker combinations instead
  of matching 'rejected' alone (avoids false positives on pre-receive hooks)
- Remove redundant -u flag from initial push; only use --set-upstream in
  fallback path when "no upstream" error is detected
- Add targetBranch as final positional arg to git branch --set-upstream-to
  so it configures the correct local branch explicitly
- Extract RemoteActionMenuItem component to deduplicate pull/push remote
  submenu rendering logic

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
apps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsx (2)

173-220: Redundant key props on RemoteActionMenuItem's returned root elements

key props placed on a component's returned root elements (lines 175, 209) have no effect on React's list reconciliation — React reads key only from the element at the call site. The keys are already correctly applied at the call sites (key={remote.name} on lines 807 and 908), so these internal ones are misleading dead code.

🧹 Remove the redundant internal keys
   if (onSetTracking) {
     return (
-      <DropdownMenuSub key={remote.name}>
+      <DropdownMenuSub>
   return (
     <DropdownMenuItem
-      key={remote.name}
       onClick={onAction}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsx`
around lines 173 - 220, Remove the redundant internal key props from the
returned root elements of the RemoteActionMenuItem component: delete
key={remote.name} on the DropdownMenuSub wrapper (the branch that returns the
submenu) and the key={remote.name} on the standalone DropdownMenuItem return
path, since keys are provided by the parent call sites and these internal keys
are ignored by React's reconciliation.

1002-1015: Sync per-remote items don't use the newly extracted RemoteActionMenuItem

The sync submenu items inline the exact same DOM structure as the plain (no-onSetTracking) variant of RemoteActionMenuItem — icon, remote name, and truncated URL. Since RemoteActionMenuItem was extracted to deduplicate this pattern, not using it here leaves an inconsistency and diverges from the stated refactor goal.

♻️ Use RemoteActionMenuItem for sync per-remote items
-                {remotes.map((remote) => (
-                  <DropdownMenuItem
-                    key={`sync-${remote.name}`}
-                    onClick={() => isGitOpsAvailable && onSyncWithRemote(worktree, remote.name)}
-                    disabled={isSyncing || !isGitOpsAvailable}
-                    className="text-xs"
-                  >
-                    <RefreshCw className="w-3.5 h-3.5 mr-2" />
-                    {remote.name}
-                    <span className="ml-auto text-[10px] text-muted-foreground max-w-[100px] truncate">
-                      {remote.url}
-                    </span>
-                  </DropdownMenuItem>
-                ))}
+                {remotes.map((remote) => (
+                  <RemoteActionMenuItem
+                    key={remote.name}
+                    remote={remote}
+                    icon={RefreshCw}
+                    trackingRemote={trackingRemote}
+                    isDisabled={isSyncing}
+                    isGitOpsAvailable={isGitOpsAvailable}
+                    onAction={() => isGitOpsAvailable && onSyncWithRemote(worktree, remote.name)}
+                  />
+                ))}

This also gains the tracking indicator for free when trackingRemote matches a remote — consistent with how pull/push per-remote items behave.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsx`
around lines 1002 - 1015, Replace the inline per-remote DropdownMenuItem JSX
inside remotes.map with the extracted RemoteActionMenuItem to avoid duplication:
for each remote, render RemoteActionMenuItem (keeping the same key) and pass the
RefreshCw icon, the remote object/name, the onClick handler () =>
isGitOpsAvailable && onSyncWithRemote(worktree, remote.name), and the disabled
prop (isSyncing || !isGitOpsAvailable); this preserves behavior and also lets
RemoteActionMenuItem show the tracking indicator when trackingRemote matches the
remote.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@apps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsx`:
- Around line 173-220: Remove the redundant internal key props from the returned
root elements of the RemoteActionMenuItem component: delete key={remote.name} on
the DropdownMenuSub wrapper (the branch that returns the submenu) and the
key={remote.name} on the standalone DropdownMenuItem return path, since keys are
provided by the parent call sites and these internal keys are ignored by React's
reconciliation.
- Around line 1002-1015: Replace the inline per-remote DropdownMenuItem JSX
inside remotes.map with the extracted RemoteActionMenuItem to avoid duplication:
for each remote, render RemoteActionMenuItem (keeping the same key) and pass the
RefreshCw icon, the remote object/name, the onClick handler () =>
isGitOpsAvailable && onSyncWithRemote(worktree, remote.name), and the disabled
prop (isSyncing || !isGitOpsAvailable); this preserves behavior and also lets
RemoteActionMenuItem show the tracking indicator when trackingRemote matches the
remote.

@gsxdsm gsxdsm merged commit 91bff21 into AutoMaker-Org:v0.15.0rc Feb 22, 2026
7 checks passed
gsxdsm added a commit to gsxdsm/automaker that referenced this pull request Feb 23, 2026
gsxdsm added a commit to gsxdsm/automaker that referenced this pull request Feb 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant